package functionroots.primitives;

/**
 * Provê uma abstração de métodos numéricos para encontrar raízes de funções
 * de uma variável real. Subclasses de RootFinder devem implementar o método
 * numérico proposto. Instâncias de RootFinder são criadas para atuar sobre
 * uma determinada função que pode ser obtida através de getFunction() e
 * alterada posteriormente por setFunction(RealFunction f). Algumas funções
 * pré-definidas estão disponíveis na interface RealFunction, mas outras podem
 * ser implementadas. Para uma visão geral das funções oferecidas, consulte
 * RealFunction.java.
 *
 * @author orion
 */
public abstract class RootFinder<D extends InitialData> {

    private RealFunction function;
    private long maxIterations = 100;
    private double precision = 1.0e-4;

    /**
     * Instancia um método numérico com uma função, precisão e número máximo de
     * iterações pré-definidos.
     * @param function a função alvo.
     * @param precision a precisão pretendida.
     * @param maxIterations o número máximo de iterações.
     */
    public RootFinder(RealFunction function, double precision, long maxIterations) {
        setFunction(function);
        setMaxIterations(maxIterations);
        setPrecision(precision);
    }

    /**
     * Instancia um método numérico com uma função e precisão pré-definidas.
     * @param function a função alvo.
     * @param precision a precisão pretendida.
     */
    public RootFinder(RealFunction function, double precision) {
        this(function, precision, Long.MAX_VALUE);
    }

    /**
     * Tenta achar a melhor aproximação possível de uma raiz a partir dos dados
     * iniciais fornecidoa. O número máximo de iterações pode impedir que a
     * precisão desejada seja alcançada.
     * @param initialData Dados iniciais para o método numérico usado.
     * @return A melhor aproximação possível de uma raiz neste intervalo.
     * @throws MaximumIterationsException quando a precisão desejada não é
     * alcançada e o algoritmo atinge o máximo de iterações permitido.
     */
    public abstract double scan(D initialData) throws MaximumIterationsException;

    /**
     * Obtém a função com a qual o método está atualmente associado.
     * @return A função com a qual o método está atualmente associado.
     */
    public RealFunction getFunction() {
        return function;
    }

    /**
     * Obtém o máximo de iterações atual.
     * @return O máximo de iterações atual.
     */
    public long getMaxIterations() {
        return maxIterations;
    }

    /**
     * Obtém a precisão atual.
     * @return A precisão atual.
     */
    public double getPrecision() {
        return precision;
    }

    /**
     * Ajusta o máximo de iterações. O algoritmo pára ao atingir o máximo de
     * iterações, ainda que a precisão desejada não tenha sido obtida.
     * @param maxIterations O máximo de iterações.
     */
    public void setMaxIterations(long maxIterations) {
        if (maxIterations <= 0) {
            throw new IllegalArgumentException("Número máximo de iterações "
                    + "deve ser um inteiro não-nulo e positivo.");
        } else {
            this.maxIterations = maxIterations;
        }
    }

    /**
     * Ajusta a precisão do algoritmo. Os critérios de "precisão" podem variar
     * conforme o método implementado. Apenas o módulo da precisão é
     * considerado. Se o valor zero for fornecido, é bem provável que o máximo
     * de iterações seja executado.
     * @param precision A precisão desejada.
     */
    public void setPrecision(double precision) {
        this.precision = (precision >= 0) ? precision : -precision;
    }

    /**
     * Ajusta a função sobre a qual o método irá trabalhar.
     * @param function A função para trabalhar.
     * @throws IllegalArgumentException caso null seja fornecido.
     */
    public void setFunction(RealFunction function) {
        if (function == null) {
            throw new IllegalArgumentException("Attempted to set function "
                    + "to null.");
        } else {
            this.function = function;
        }

    }
}
